/**
 * Project Name:pay-commons-core
 * File Name:SimpleHttpUtil.java
 * Package Name:com.zfull.commons.utils.httpclient
 * Date:2018年2月1日上午10:54:24
 * Copyright (c) 2018, www.zengfull.com All Rights Reserved.
 *
*/

package com.rc.saas.payment.unwxpay.util;

import org.apache.commons.lang3.StringUtils;
import org.apache.http.Header;
import org.apache.http.HttpEntity;
import org.apache.http.HttpHost;
import org.apache.http.client.config.RequestConfig;
import org.apache.http.client.config.RequestConfig.Builder;
import org.apache.http.client.entity.UrlEncodedFormEntity;
import org.apache.http.client.methods.CloseableHttpResponse;
import org.apache.http.client.methods.HttpGet;
import org.apache.http.client.methods.HttpPost;
import org.apache.http.client.methods.HttpRequestBase;
import org.apache.http.conn.ssl.SSLConnectionSocketFactory;
import org.apache.http.entity.StringEntity;
import org.apache.http.impl.client.CloseableHttpClient;
import org.apache.http.impl.client.HttpClients;
import org.apache.http.impl.conn.PoolingHttpClientConnectionManager;
import org.apache.http.message.BasicNameValuePair;
import org.apache.http.ssl.SSLContextBuilder;
import org.apache.http.ssl.SSLContexts;
import org.apache.http.ssl.TrustStrategy;
import org.apache.http.util.EntityUtils;

import javax.net.ssl.SSLContext;
import java.io.File;
import java.io.FileInputStream;
import java.io.IOException;
import java.net.URLEncoder;
import java.nio.charset.Charset;
import java.security.*;
import java.security.cert.CertificateException;
import java.security.cert.X509Certificate;
import java.util.ArrayList;
import java.util.HashMap;
import java.util.List;
import java.util.Map;
import java.util.concurrent.ConcurrentHashMap;


/**
 * HTTPCLIENT 访问工具,可识别HTTP/HTTPS<br/>
 * ClassName:SimpleHttpUtil <br/>
 * Function: TODO ADD FUNCTION. <br/>
 * Reason: TODO ADD REASON. <br/>
 * Date: 2018年2月1日 上午10:54:24 <br/>
 * 
 * @author GuangMing
 * @version V1.0.0
 * @since JDK 1.8 
 */
public class SimpleHttpUtil {
	public static final String MSG_TYPE_KEYVALUE = "KeyAndValue";
	
	/** 代理标志 */
	private boolean useProxy = false;
	/** 代理地址 */
	private String proxyHost;
	/** 代理端口 */
	private Integer proxyPort;
	/** 请求地址 */
	private String url;
	/** 默认超时时间2分钟 */
	private Integer timeout = 120000;
	/** 默认请求类型POST */
	private MethodType methodType = MethodType.POST;
	/** 默认字符编码 */
	private String charSet = "UTF-8";
	/** 请求端口 */
	private Integer port = -1;
	private boolean isConnPool = false;

	/** 最大连接数 */
	private static int DEFAULT_CONNECTION_TATOL = 1000;
	/** 单路由最大连接数 */
	private static int DEFAULT_PER_ROUTE = 25;
	public static PoolingHttpClientConnectionManager clientConnectionManager = null;
	private static Map<String, PoolingHttpClientConnectionManager> poolConnManagerMap = new ConcurrentHashMap<String, PoolingHttpClientConnectionManager>();

	/** 实例化 HTTPCLIENT 连接参数 */
	private Map<String, String> paramMap = null;
	/** 需要发送的数据 */
	private Map<String, String> sendData;

	public SimpleHttpUtil() {
	}

	public SimpleHttpUtil(Map<String, String> paramMap) {
		if (this.paramMap == null) {
			this.paramMap = paramMap;
			if (getConnectionManager(paramMap) != null)
				isConnPool = true;
		}
	}

	public synchronized static PoolingHttpClientConnectionManager getConnectionManager(Map<String, String> paramMap) {
		if (StringUtils.isEmpty(paramMap.get("connChnlNo")))
			return null;

		PoolingHttpClientConnectionManager poolConnManager = poolConnManagerMap.get(paramMap.get("connChnlNo"));
		if (poolConnManager == null) {
			int maxTotal = paramMap.get("connMaxTotal") != null ? Integer.parseInt(paramMap.get("connMaxTotal"))
					: DEFAULT_CONNECTION_TATOL;
			int maxPerRoute = paramMap.get("connMaxPerRoute") != null
					? Integer.parseInt(paramMap.get("connMaxPerRoute")) : DEFAULT_PER_ROUTE;

			poolConnManager = new PoolingHttpClientConnectionManager();
			poolConnManager.setMaxTotal(maxTotal);
			poolConnManager.setDefaultMaxPerRoute(maxPerRoute);
			poolConnManagerMap.put((String) paramMap.get("connChnlNo"), poolConnManager);


			// 扫描无效连接的线程
			IdleConnectionMonitorThread scanThread = new IdleConnectionMonitorThread(poolConnManager);
			scanThread.start();
		}

		return poolConnManager;
	}

	/**
	 * 使用默认参数发送信息
	 * 
	 * @Title: send. <br/>
	 * @Description: TODO(这里用一句话描述这个方法的作用).<br/>
	 * @Date: 2018年2月1日 上午11:36:51 <br/>
	 * @author GuangMing
	 * @return
	 * @throws Exception
	 * @since JDK 1.8
	 */
	public String send() throws Exception {
		StringBuilder builder = new StringBuilder();
		String reqData = "";
		if (this.sendData != null && this.sendData.size() > 0) {
			for (Map.Entry<String, String> item : this.sendData.entrySet()) {
				builder.append(item.getKey()).append("=").append(URLEncoder.encode(item.getValue(), this.charSet))
						.append("&");
			}
			reqData = builder.substring(0, builder.length() - 1);
		}
		return send(this.url, this.port, this.charSet, this.timeout, reqData, this.methodType);
	}

	/**
	 * 
	 * @Title: send. <br/>
	 * @Description: TODO(这里用一句话描述这个方法的作用).<br/>
	 * @Date: 2018年2月1日 上午11:39:02 <br/>
	 * @author GuangMing
	 * @param ipAddress
	 *            可为完整请求URL地址,也可为IP
	 * @param port
	 *            当ipAddress为IP时必填,其他可为NULL
	 * @param charset
	 *            字符集
	 * @param timeout
	 *            请求超时时间(单位：毫秒)
	 * @param reqData
	 *            请求数据
	 * @param methodType
	 *            请求类型
	 * @return
	 * @throws Exception
	 * @since JDK 1.8
	 */
	public String send(String ipAddress, Integer port, String charset, Integer timeout, String reqData,
			MethodType methodType) throws Exception {
		Map<String, String> head = new HashMap<String, String>();
		if (methodType == MethodType.GET) {
			head.put("Content-Type", "text/html;charset=" + charset);
		} else if (methodType == MethodType.POST) {
			head.put("Content-Type", "application/x-www-form-urlencoded;charset=" + charset);
			head.put("Connection", "close");// 短连接
		}
		return send(ipAddress, port, charset, timeout, reqData, head, methodType);
	}

	/**
	 * 
	 * @Title: send. <br/>
	 * @Description: TODO(这里用一句话描述这个方法的作用).<br/>
	 * @Date: 2018年2月1日 上午11:43:00 <br/>
	 * @author GuangMing
	 * @param url
	 *            完整请求URL地址
	 * @param charset
	 *            字符集
	 * @param timeout
	 *            请求超时时间(单位：毫秒)
	 * @param reqData
	 *            请求数据
	 * @param methodType
	 *            请求类型
	 * @return
	 * @throws Exception
	 * @since JDK 1.8
	 */
	public String send(String url, String charset, Integer timeout, String reqData, MethodType methodType)
			throws Exception {
		return send(url, 80, charset, timeout, reqData, methodType);
	}

	/**
	 * 默认字符UTF-8请求
	 * 
	 * @Title: send. <br/>
	 * @Description: TODO(这里用一句话描述这个方法的作用).<br/>
	 * @Date: 2018年2月1日 上午11:44:15 <br/>
	 * @author GuangMing
	 * @param url
	 *            完整请求URL地址
	 * @param timeout
	 *            请求超时时间(单位：毫秒)
	 * @param reqData
	 *            请求数据
	 * @param methodType
	 *            请求类型
	 * @return
	 * @throws Exception
	 * @since JDK 1.8
	 */
	public String send(String url, Integer timeout, String reqData, MethodType methodType) throws Exception {
		return send(url, charSet, timeout, reqData, methodType);
	}

	/**
	 * 默认超时为2分钟的请求
	 * 
	 * @Title: send. <br/>
	 * @Description: TODO(这里用一句话描述这个方法的作用).<br/>
	 * @Date: 2018年2月1日 上午11:46:29 <br/>
	 * @author GuangMing
	 * @param url
	 *            完整请求URL地址
	 * @param reqData
	 *            请求数据
	 * @param charSet
	 *            字符集
	 * @param methodType
	 *            请求类型
	 * @return
	 * @throws Exception
	 * @since JDK 1.8
	 */
	public String send(String url, String reqData, String charSet, MethodType methodType) throws Exception {
		return send(url, charSet, timeout, reqData, methodType);
	}

	/**
	 * 按照数据MAP集合请求
	 * 
	 * @Title: send. <br/>
	 * @Description: TODO(这里用一句话描述这个方法的作用).<br/>
	 * @Date: 2018年2月1日 上午11:50:18 <br/>
	 * @author GuangMing
	 * @param url
	 *            完整请求URL地址
	 * @param dataMap
	 *            请求数据
	 * @param charSet
	 *            字符集
	 * @param methodType
	 *            请求类型
	 * @return
	 * @throws Exception
	 * @since JDK 1.8
	 */
	public String send(String url, Map<String, String> dataMap, String charSet, MethodType methodType)
			throws Exception {
		StringBuilder builder = new StringBuilder();
		String reqData = "";
		if (dataMap != null && dataMap.size() > 0) {
			for (Map.Entry<String, String> item : dataMap.entrySet()) {
				builder.append(item.getKey()).append("=").append(URLEncoder.encode(item.getValue(), charSet))
						.append("&");
			}
			reqData = builder.substring(0, builder.length() - 1);
		}
		return send(url, reqData, charSet, methodType);
	}

	public String send(String ipAddress, Integer port, String charset, Integer timeout, String data,
			Map<String, String> head, MethodType methodType) throws Exception {
		Builder requestConfigBuilder = RequestConfig.custom().setSocketTimeout(timeout).setConnectTimeout(timeout);

		if (this.useProxy) {
			HttpHost proxy = new HttpHost(proxyHost, proxyPort);
			requestConfigBuilder.setProxy(proxy);
		}

		HttpRequestBase method = null;
		// 设置请求URL
		String sendURL = ipAddress.toLowerCase().indexOf("http") >= 0 ? ipAddress : "http://" + ipAddress + ":" + port;

		CloseableHttpClient httpClient = initHttpClient(sendURL);

		// 根据请求方式选择请求方法
		String result = null;
		switch (methodType) {
		case GET:
			HttpGet httpGet = new HttpGet(sendURL + "?" + data);
			method = httpGet;
			break;

		case POST:
			HttpPost httpPost = new HttpPost(sendURL);
			httpPost.setEntity(
					new StringEntity(data, Charset.forName(StringUtils.isEmpty(charset) ? "UTF-8" : charset)));
			method = httpPost;
			break;

		case POST_KEY_VALUE:
			HttpPost httpPostKeyValue = new HttpPost(sendURL);

			Map<String, String> sendDatas = toQueryMap(data);
			List<BasicNameValuePair> paramList = new ArrayList<BasicNameValuePair>();
			for (Map.Entry<String, String> item : sendDatas.entrySet()) {
				paramList.add(new BasicNameValuePair(item.getKey(), item.getValue()));
			}
			if (null == charset) {
				charset = "UTF-8";
			}
			httpPostKeyValue.setEntity(new UrlEncodedFormEntity(paramList, charset));
			method = httpPostKeyValue;// 补充
			break;
		}

		// 设置头信息
		if (null != head) {
			for (Map.Entry<String, String> item : head.entrySet()) {
				method.setHeader(item.getKey(), item.getValue());
			}
		}

		// 发送数据
		CloseableHttpResponse response = null;
		try {
			// 4.3专用设置超时
			method.setConfig(initRequestConfig(paramMap));

			if (isConnPool) {
				System.out.println("[httpClient pool status-before execute]"
						+ poolConnManagerMap.get(paramMap.get("connChnlNo")).getTotalStats());
			}
			response = httpClient.execute(method);
			if (isConnPool) {
				System.out.println("[httpClient pool status-after  execute]"
						+ poolConnManagerMap.get(paramMap.get("connChnlNo")).getTotalStats());
			}

			this.statusCode = response.getStatusLine().getStatusCode();
			this.location = null != response.getFirstHeader("Location") ? response.getFirstHeader("Location").getValue()
					: "";
			Header responseHead = response.getFirstHeader("Content-Type");

			if (responseHead != null) {
				int index;
				if ((index = responseHead.getValue().indexOf("charset")) >= 0) {
					charset = responseHead.getValue().substring(index + 8);
				}
				if(charset.toUpperCase().contains("UTF-8"))
					charset = "UTF-8";
			}

			result = EntityUtils.toString(response.getEntity(), Charset.forName(charset));
			System.out.println("SEND STATUS[" + response + "]  RESULT [" + result + "]");

		} catch (Exception e) {
			e.printStackTrace();
			try {
				if (response != null) {
					HttpEntity entity = response.getEntity();
					if (entity != null) {
						EntityUtils.consume(entity);
					}
				}
			} catch (IOException ee) {
			}
			method.abort();

			throw e;
		} finally {
			try {
				if (response != null)
					response.close();
			} catch (IOException e) {
				e.printStackTrace();
			}
			method.releaseConnection();

			if (!isConnPool) {
				httpClient.close();
			}
		}
		return result;
	}

	private RequestConfig initRequestConfig(Map<String, String> timeMap) {
		int connTimeout = 30000;
		int readTimeout = 30000;
		if (timeMap != null) {
			connTimeout = timeMap.get("connTimeout") != null ? Integer.parseInt((String) timeMap.get("connTimeout"))
					: 30000;
			readTimeout = timeMap.get("readTimeout") != null ? Integer.parseInt((String) timeMap.get("readTimeout"))
					: 30000;
		}
		RequestConfig requestConfig = RequestConfig.custom().setConnectionRequestTimeout(connTimeout)
				.setSocketTimeout(readTimeout).setConnectTimeout(connTimeout).build();

		return requestConfig;
	}

	/** SSL证书路径 */
	private String sslKeyPath;
	/** SSL证书密码 */
	private String sslKeyPass;
	/** SSL证书类型 */
	private String sslKeyType = "PKCS12";
	/** SSL证书协议 SSLv2Hello, SSLv3, TLSv1, TLSv1.2 */
	private String sslProtocol = "TLSv1";

	/**不对外提供*/
	@SuppressWarnings("static-access")
	private CloseableHttpClient initHttpClient(String sendURL) throws KeyManagementException, NoSuchAlgorithmException,
			KeyStoreException, UnrecoverableKeyException, CertificateException, IOException {
		if (sendURL.toLowerCase().indexOf("https") >= 0) {
			if (StringUtils.isNotEmpty(sslKeyPath) && StringUtils.isNotEmpty(sslKeyPass))
				return initUseCertificateHttpClient();
			else
				return initUnUseCertificateHttpClient();
		}

		if (isConnPool && paramMap != null && poolConnManagerMap.get(paramMap.get("connChnlNo")) != null) {
			return HttpClients.custom().setConnectionManager(poolConnManagerMap.get(paramMap.get("connChnlNo")))
					.disableAutomaticRetries().build();
		} else
			return HttpClients.custom().create().disableAutomaticRetries().build();
	}
	
	/**
	 * 初始化HttpClient
	 * @Title: initHttpClientUnCert. <br/>
	 * @Description: TODO(这里用一句话描述这个方法的作用).<br/>
	 * @Date: 2018年2月3日 下午2:23:01 <br/>
	 * @author GuangMing
	 * @param sslKeyPath
	 * @param sslKayPass
	 * @return
	 * @since JDK 1.8
	 */
	public CloseableHttpClient initHttpClientUnCert(String sslKeyPath, String sslKeyPass) {
		try {
			if (StringUtils.isNotEmpty(sslKeyPath) && StringUtils.isNotEmpty(sslKeyPass)) {
				setSSLInfo(sslKeyPath, sslKeyPass);
				return initUseCertificateHttpClient();
			} else
				return initUnUseCertificateHttpClient();
		} catch (Exception e) {
			System.out.println("初始化证书失败.");
			return HttpClients.createDefault();
		}
	}

	public void setSSLInfo(String sslKeyPath, String sslKayPass) {
		this.sslKeyPass = sslKayPass;
		this.sslKeyPath = sslKeyPath;
	}
	public void setSSLInfo(String sslKeyPath, String sslKayPass, String sslKeyType) {
		setSSLInfo(sslKeyPath, sslKayPass);
		this.sslKeyType = sslKeyType;
	}
	public void setSSLInfo(String sslKeyPath, String sslKayPass, String sslKeyType, String sslProtocol) {
		setSSLInfo(sslKeyPath, sslKayPass, sslKeyType);
		this.sslProtocol = sslProtocol;
	}

	
	
	
	/**
	 * 初始化证书 httpClient
	 * 
	 * @Title: initUseCertificateHttpClient. <br/>
	 * @Description: TODO(这里用一句话描述这个方法的作用).<br/>
	 * @Date: 2018年2月1日 下午12:36:28 <br/>
	 * @author GuangMing
	 * @return
	 * @throws KeyStoreException
	 * @throws NoSuchAlgorithmException
	 * @throws CertificateException
	 * @throws IOException
	 * @throws KeyManagementException
	 * @throws UnrecoverableKeyException
	 * @since JDK 1.8
	 */
	private CloseableHttpClient initUseCertificateHttpClient() throws KeyStoreException, NoSuchAlgorithmException,
			CertificateException, IOException, KeyManagementException, UnrecoverableKeyException {
		FileInputStream instream = null;
		try {
			KeyStore keyStore = KeyStore.getInstance(sslKeyType);
			instream = new FileInputStream(new File(sslKeyPath));
			keyStore.load(instream, sslKeyPass.toCharArray());
			SSLContext sslcontext = SSLContexts.custom().loadKeyMaterial(keyStore, sslKeyPass.toCharArray()).build();
			@SuppressWarnings("deprecation")
            SSLConnectionSocketFactory sslsf = new SSLConnectionSocketFactory(sslcontext, new String[] { sslProtocol },
					null, SSLConnectionSocketFactory.ALLOW_ALL_HOSTNAME_VERIFIER); // 忽略证书验证

			if (isConnPool && paramMap != null && poolConnManagerMap.get(paramMap.get("connChnlNo")) != null) {
				return HttpClients.custom().setConnectionManager(poolConnManagerMap.get(paramMap.get("connChnlNo")))
						.setSSLSocketFactory(sslsf).disableAutomaticRetries().build();
			} else {
				return HttpClients.custom().setSSLSocketFactory(sslsf).disableAutomaticRetries().build();
			}

		} finally {
			if (null != instream) {
				try {
					instream.close();
				} catch (IOException e) {
					e.printStackTrace();
				}
			}
		}
	}

	/**
	 * 初始化绕过证书 httpClient
	 * 
	 * @Title: initUnUseCertificateHttpClient. <br/>
	 * @Description: TODO(这里用一句话描述这个方法的作用).<br/>
	 * @Date: 2018年2月1日 下午12:38:05 <br/>
	 * @author GuangMing
	 * @return
	 * @throws KeyManagementException
	 * @throws NoSuchAlgorithmException
	 * @throws KeyStoreException
	 * @since JDK 1.8
	 */
	private CloseableHttpClient initUnUseCertificateHttpClient()
			throws KeyManagementException, NoSuchAlgorithmException, KeyStoreException {
		SSLContext sslContext = new SSLContextBuilder().loadTrustMaterial(null, new TrustStrategy() {
			public boolean isTrusted(X509Certificate[] chain, String authType) throws CertificateException {
				return true;
			}
		}).build();
		@SuppressWarnings("deprecation")
        SSLConnectionSocketFactory sslConnectionSocketFactory = new SSLConnectionSocketFactory(sslContext,
				SSLConnectionSocketFactory.ALLOW_ALL_HOSTNAME_VERIFIER); // 忽略证书验证

		if (isConnPool && paramMap != null && poolConnManagerMap.get(paramMap.get("connChnlNo")) != null) {
			return HttpClients.custom().setConnectionManager(poolConnManagerMap.get(paramMap.get("connChnlNo")))
					.setSSLSocketFactory(sslConnectionSocketFactory).disableAutomaticRetries().build();
		} else {
			return HttpClients.custom().setSSLSocketFactory(sslConnectionSocketFactory).disableAutomaticRetries()
					.build();
		}

	}

	/**
	 * 设置代理
	 * 
	 * @param proxyAddress
	 *            代理地址
	 * @param proxyPort
	 *            代理端口
	 */
	public void setProxy(String proxyAddress, Integer proxyPort) {
		this.useProxy = true;
		this.proxyHost = proxyAddress;
		this.proxyPort = proxyPort;
	}
	
	public void setSendData(String sendData) {
		Map<String, String> sendInfo = new HashMap<String, String>();
		String[] sendDates = sendData.split("&");
		int index;
		for (String string : sendDates) {
			index = string.indexOf("=");
			if (index == -1) {
				sendInfo.put(string, "");
			} else {
				sendInfo.put(string.substring(0, index), string.substring(index + 1, string.length()));
			}
		}
		this.sendData = sendInfo;
	}

	public void setSendData(Map<String, String> sendData) throws Exception {
		this.sendData = sendData;
	}

	public void setURL(String url) {
		this.url = url;
	}

	public void setTimeout(Integer timeout) {
		this.timeout = timeout;
	}

	public void setMethodType(MethodType methodType) {
		this.methodType = methodType;
	}

	public void setCharset(String charset) throws Exception {
		this.charSet = charset;
	}

	public void setPort(Integer port) {
		this.port = port;
	}

	private int statusCode;
	private String location;

	public int getStatusCode() {
		return statusCode;
	}
	public String getLocation() {
		return location;
	}
	
    public static Map<String, String> toQueryMap(String paramStr) {
        String[] vals = split(paramStr, "&");
        Map<String, String> resultMap = new HashMap<String, String>(vals.length);
        int index;
        for (int i = 0; i < vals.length; i++) {
            index = vals[i].indexOf("=");
            resultMap.put(vals[i].substring(0, index), vals[i].substring(index + 1, vals[i].length()));
        }
        return resultMap;
    }
    
    public static String[] split(String msg, String cut) {
        int index = 0;
        int befault = 0;
        List<String> l = new ArrayList<String>(128);
        do {
            index = msg.indexOf(cut, index);
            l.add(msg.substring(befault, index == -1 ? msg.length() : index));
            index += 1;
            befault = index + cut.length() - 1;
        } while (index > 0 && index < msg.length());
        return l.toArray(new String[l.size()]);
    }
}

